#include "CFollowersQueryInc.h"
#include <wx/msgdlg.h>
#include "Utils.h"
#include <utility>
#include <exception>

#ifdef GUI_DEPENDENT
CFollowersQueryInc::CFollowersQueryInc(wxEvtHandler* pGaugeEventReciever)
    :CQuery(pGaugeEventReciever)
#else
CFollowersQueryInc::CFollowersQueryInc()
    :CQuery()
#endif
    ,myReq(true)
    ,reqBLOB(false)

{
        createTempTable();
}

CFollowersQueryInc::~CFollowersQueryInc()
{
}

bool CFollowersQueryInc::createTempTable()
{
    try
    {
        if(!(db.TableExists("T_current_ids")))
        {
            db.ExecuteUpdate(CREATE_TABLE_CURRENT_IDS);
        }
        else
        {
            db.ExecuteUpdate("DROP TABLE T_current_ids");
            db.ExecuteUpdate(CREATE_TABLE_CURRENT_IDS);
        }

        if(!(db.TableExists("T_friend_ids")))
        {
            db.ExecuteUpdate(CREATE_TABLE_FRIEND_IDS);
        }
        else
        {
            db.ExecuteUpdate("DROP TABLE T_friend_ids");
            db.ExecuteUpdate(CREATE_TABLE_FRIEND_IDS);
        }
    }
    catch(wxSQLite3Exception e)
    {
        #ifdef SQL_DEBUG
            wxMessageBox(wxString("When create 2 temp ids tables, ")<<(e.GetErrorCode()+":"+e.GetMessage()));
        #endif
        #ifdef SQL_LOG
            LOGMAN.logError(wxString("When create 2 temp ids tables, ")<<(e.GetErrorCode()+":"+e.GetMessage()));
        #endif
        return false;
    }

    return true;
}

bool CFollowersQueryInc::fetchAndStore(const wxString& apiURL,
                                                        const wxString& user_id,
                                                        const wxString& tableName,
                                                        int countStart,
                                                        long nextCursor,    //Default value = -1
                                                        long goalNum)       //Default value = -1
{
    //HTTP Req OBJ must be shared in class rather than between instances
    //CHttpReqLimited myReq(true);
    CUser followers;

    int itemCount = countStart;
    int retryCount = 0;
    int8_t XMLstatus = -1;
    wxString fullAPIurl(apiURL);

    do
    {
        fullAPIurl = apiURL;
        if(myReq.startReqLimited(fullAPIurl<<wxString(_T("?user_id="))<<user_id,itemCount,nextCursor))
        {
            if(followers.startParsing(myReq.getResult()))
            {
                #if defined(XML_WTF)
                //Sonetimes , when you request a count==50 XML, Sina gives a result of count==2
                //So you must try again in case that this fu*king thing happens
                if(goalNum!=-1)
                {
                    //nextCursor=-1 used to prevent break loop where condition is nextCursor==0(which means no next page)
                    ///NOTE: when you provide count=50 , the feedback acutally gives you 49 items, and next cursor = 50
                    long startNum = nextCursor==-1 ? 0 : nextCursor;
                    size_t targetNum = (startNum+itemCount-1>goalNum) ? goalNum-startNum : itemCount-1;

                    #ifdef MT_DEBUG_LOG
                    LOGMAN.logInfo(wxString("[MT DEBUG]. ")
                                            <<wxString("Target items : ")<<targetNum
                                            <<wxString(", recieved items : ")<<followers.getUser().size()
                                            <<wxString(", URL : ")<<fullAPIurl
                                            <<wxString(", currentCursor:")<<nextCursor
                                            <<wxString(", count:")<<itemCount
                                            );
                    #endif

                    if(followers.getUser().size() < targetNum && retryCount<=MAX_RETRY_ON_CORRUPT)
                    {
                    /********************************************************************
                        1.if XML structure corrupted in transmiting,there'll be extra item tags from XML parsing,
                            for example <geo_enabled></geo_enab> will be parsed as :
                            <geo_enabled></geo_enabled>
                            <geo_enab></geo_enab>
                            which will result in a fatal error when SQL INSERT

                        2.Besides, sina will include all the deleted accouts then gives you 50.
                            (When use count=50, it should get you a 49. And plus the zombie, WTF !)

                        3.if UserSize < targetNum, means the server gives what's not all you want

                            What's worse, sometimes the next_cursor hint is totally lost from XML feedback
                            Then you get the init cursor value of 0, and the while loop terminates.
                            So you actually just get 1 page, then use update (default) from top of followers stack
                            Your whole db data is corrupted now. Cheers !
                        *********************************************************************/
                        ++retryCount;
                        LOGMAN.resetErrorCount();   ///When retry, previous error count should be tolerated
                        XMLstatus = 1<<DATA_CORRUPTED;
                        #ifdef HTTP_LOG
                        LOGMAN.logInfo(wxString("Some items in recieved data missed.  ")
                                                <<wxString("Target items : ")<<targetNum
                                                <<wxString(", recieved items : ")<<followers.getUser().size()
                                                <<wxString(", retryCount : ")<<retryCount
                                                <<wxString(", URL : ")<<fullAPIurl
                                                <<wxString(", currentCursor:")<<nextCursor
                                                <<wxString(", count:")<<itemCount
                                                );
                        #endif
                    }
                    else if(retryCount>MAX_RETRY_ON_CORRUPT)
                    {
                        #ifdef HTTP_LOG
                        LOGMAN.logError(wxString("Recieved data corrupted, retry limit reached. ")
                                                <<wxString("Target items : ")<<targetNum
                                                <<wxString(", recieved items : ")<<followers.getUser().size()
                                                <<wxString(", URL : ")<<fullAPIurl
                                                <<wxString(", currentCursor:")<<nextCursor
                                                <<wxString(", count:")<<itemCount
                                                );
                        #endif
                        return false;
                    }
                    else if(followers.getUser().size() >= targetNum)
                    {
                        //Data fetched is of correct quality
                        XMLstatus = 1<<DATA_VALID;
                        ///Only a successful SQL INSERT can prove there's no data corruption including XML structure integrity
                        XML_SANITY_CHECK(followers.getUser());
                        if(store(followers.getUser(),tableName, goalNum))
                        {
                            //nextCursor = followers.next_cursor;
                            //Don't trust what a dull server says, do calc yourself
                            nextCursor = (startNum+itemCount-1>=goalNum)?followers.next_cursor : startNum+itemCount;
                        }
                        else
                        {

                            ++retryCount;
                            LOGMAN.resetErrorCount();   ///When retry, previous error count should be tolerated
                            #ifdef HTTP_LOG
                            LOGMAN.logInfo(wxString("XML structure corrupted. ")
                                                    <<wxString("Target items : ")<<targetNum
                                                    <<wxString(", recieved items : ")<<followers.getUser().size()
                                                    <<wxString(", retryCount : ")<<retryCount
                                                    <<wxString(", URL : ")<<fullAPIurl
                                                    <<wxString(", currentCursor:")<<nextCursor
                                                    <<wxString(", count:")<<itemCount
                                                    );
                            #endif
                        }
                    }
                }
                else
                {
                    //Used in a non-init situation
                    XMLstatus = 1<<STATUS_NOT_USED;
                    //It's not necessary to calc nextCursor in situations which is not init record data
                    nextCursor = followers.next_cursor;
                }
                #else   //if not XML_WTF, the world is beautiful
                XML_SANITY_CHECK(followers.getUser());
                nextCursor = followers.next_cursor;
                #endif  //XML_WTF

                //store data when it is valid
                if(
                   #ifdef XML_WTF
                   (XMLstatus == 1<<STATUS_NOT_USED) &&
                   #endif
                   !store(followers.getUser(),tableName, goalNum))
                {
                    return false;
                }

            }
            else
            {
                //In case that parsing could be failed and always such that nextCursor!=0 , then inifinitely loop
                #ifdef XML_LOG
                LOGMAN.logError(wxString("XML parsing failed"));
                #endif
                ++retryCount;
                LOGMAN.resetErrorCount();   ///When retry, previous error count should be tolerated
                if(retryCount>MAX_RETRY_ON_CORRUPT) return false;
            }
        }
        else
        {
            #ifdef _DEBUG
                wxMessageBox(wxString("Unable to connect, ")<<wxString("error code: ")<<myReq.httpErrCode);
            #endif
            #ifdef HTTP_LOG
                LOGMAN.logError(wxString("Unable to connect, ")
                                    <<wxString("error code: ")<<myReq.httpErrCode
                                    <<wxString(" || URL : ")
                                    <<apiURL<<wxString(_T("?&user_id="))<<user_id
                                    <<wxString(" , using count : ")<<itemCount
                                    <<wxString(" , using cursor : ")<<nextCursor);
            #endif
            return false;
        }

    }while( nextCursor !=0 );

    //It's reported that this 100% is not always reached , so at end here I forced it to 100%
    #ifdef GUI_DEPENDENT
    SUB_GAUGE_UPDATE("", 100, gaugeEventReciever, __LINE__);
    #endif

    return true;
}

bool CFollowersQueryInc::fetchAndStoreBLOB(const wxString& apiURL,
                                                                    const wxString& user_id,
                                                                    const wxString& tableName,
                                                                    const wxString& colName)
{
    wxString whereExpr(" id='");
    whereExpr<<user_id<<wxString("'");

    if(reqBLOB.startReqBLOB(apiURL))
    {
        wxMemoryBuffer* blobValue = reqBLOB.getMemBuf();
        if(blobValue != NULL)
        {
            return storeBLOB(*blobValue, tableName, colName, whereExpr);
        }
        else
        {
            #ifdef SQL_LOG
                LOGMAN.logError("When fetchAndStoreBLOB , blobValue is null");
            #endif
        }
    }

    return false;
}

tableType& CFollowersQueryInc::compare(const wxString& recordTableName)
{
    wxString sqlStr("");
    sqlStr<<wxString("SELECT * FROM ")<<recordTableName
            <<wxString(" WHERE id NOT IN (SELECT id FROM T_current_ids)");
    return query(sqlStr);
}

bool CFollowersQueryInc::intoRecycler(tableType& infoTable)
{
    if(infoTable.size()!=0)
    {
        hasFollowing(infoTable);
        if(store(infoTable, "T_followers_recycler"))
        {
            std::vector< std::map<wxString,wxString> >::iterator itr;
            for(itr = infoTable.begin(); itr!=infoTable.end();++itr)
            {
                wxString jpgURL = (*itr)["profile_image_url"].Remove(0,7);  // remove ("http://") in url
                fetchAndStoreBLOB(jpgURL, (*itr)["id"], "T_followers_recycler", "profile_image");
            }
            return true;
        }
    }
    return false;
}

// This func will directly modify the <following> field
// for internal use in intoRecycler
void CFollowersQueryInc::hasFollowing(tableType& infoTable)
{
    wxString ids("");
    std::vector< std::map<wxString,wxString> >::iterator itr;
    for(itr = infoTable.begin(); itr!=infoTable.end(); ++itr)
    {
        query(wxString("SELECT id FROM T_friend_ids WHERE id='")<<(*itr)["id"]<<"'");
        if(result.size()!=0)
        {
            (*itr)["following"] = "true";
        }
    }
}


unsigned long CFollowersQueryInc::countNewFollowers(const wxString& tableName)
{
    unsigned long count = 0;

    wxString countStr("");
    wxString sqlStr("SELECT COUNT(id) FROM T_current_ids WHERE id NOT IN (SELECT id FROM ");
    sqlStr+= tableName + ")";

    try
    {
        countStr = query(sqlStr)[0]["COUNT(id)"];
        countStr.ToULong(&count);
    }
    catch(wxSQLite3Exception e)
    {
        #ifdef SQL_DEBUG
            wxMessageBox(wxString("When count new followed, ")<<(e.GetErrorCode()+":"+e.GetMessage()));
        #endif
        #ifdef SQL_LOG
            LOGMAN.logError(wxString("When count new followed, ")<<(e.GetErrorCode()+":"+e.GetMessage()));
        #endif
    }
    catch(...)
    {
        #ifdef _DEBUG
            wxMessageBox("When count new followed , get a non-SQL error");
        #endif
        #ifdef SQL_LOG
            LOGMAN.logError("When count new followed , get a non-SQL error");
        #endif
    }

    return count;
}

bool CFollowersQueryInc::unfollow(const wxString& targetUserID)
{
    if(!targetUserID.IsEmpty())
    {
        if(myReq.startReqLimited(wxString("http://api.t.sina.com.cn/friendships/destroy/:")<<targetUserID<<".xml",0,0,"nothing"))
        {
            try
            {
                db.ExecuteUpdate(wxString("UPDATE OR REPLACE T_followers_recycler SET following='false' WHERE id='")<<targetUserID<<"'");
            }
            catch(wxSQLite3Exception e)
            {
                #ifdef SQL_DEBUG
                    wxMessageBox(wxString("When unfollow someone, SQL error:")<<(e.GetErrorCode()+":"+e.GetMessage()));
                #endif
                #ifdef SQL_LOG
                    LOGMAN.logError(wxString("When unfollow someone, SQL error:")<<(e.GetErrorCode()+":"+e.GetMessage()));
                #endif
                return false;
            }

            return true;
        }
        else
        {
            #ifdef _DEBUG
                wxMessageBox(wxString("When unfollow someone, http error:")<<myReq.httpErrCode);
            #endif
            #ifdef SQL_LOG
                LOGMAN.logError(wxString("When unfollow someone, http error:")<<myReq.httpErrCode);
            #endif
            return false;
        }
    }

    return false;
}
/************************* Class API *****************************/
bool CFollowersQueryInc::initRecordData(const wxString&user_id)
{

    #ifdef GUI_DEPENDENT
    SUB_GAUGE_UPDATE(wxString(_T("获取最新粉丝...")), -1, gaugeEventReciever, __LINE__);
    SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
    #endif

    //fetch ids , complicated here....
    /*****************************************************************************
    The early design is put the record data init first then fetch id list. But here are some changes.
    When init record failed , fetch id here seems to be in vain , wasting time.
    But this can bring an up to date followers_count , which is the only way to be accurate
    Otherwise , it has to get followers_count from T_system , which is just sync to the last connection
    And if there're new followers since last sync , as you were told goal num=160 but recieved 161 or 162 ...

    Getting an up to date followers_count is not noly for GUI behaviour,
    but also , which is more important , for the later implement : resume since last sync break.
    (When you get the whole list , you will know where you are and how long to go)
    *****************************************************************************/
    if(fetchAndStore("http://api.t.sina.com.cn/followers/ids.xml", user_id, "T_current_ids", GET_ID_COUNT) &&
       fetchAndStore("http://api.t.sina.com.cn/friends/ids.xml", user_id, "T_friend_ids", GET_ID_COUNT))
    {
        #ifdef GUI_DEPENDENT
        SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
        #endif

        //count num must be stored into system table and by the way used as a goal num
        //so this goal num is not only part of GUI behaviour
        long goalNum = -1;
        goalNum = countTable("T_current_ids");
        setSystemParam("followers_count", (wxString("")<<goalNum));
        try
        {
            if(db.ExecuteQuery(_T("SELECT id FROM T_followers_record LIMIT 1")).IsNull(0) || getSystemParam("recordCorrupted")=="true")
            {
                #ifdef SQL_DEBUG
                    wxMessageBox("Initializing followers_record");
                #endif
                #ifdef SQL_DEBUG_LOG
                    logger.logInfo("Initializing followers_record");
                #endif

                //fetch full data for T_followers_record
                #ifdef GUI_DEPENDENT
                TOTAL_GAUGE_PULSE(gaugeEventReciever);
                SUB_GAUGE_UPDATE(wxString(_T("获取粉丝数据中...")), -1, gaugeEventReciever, __LINE__);
                TOTAL_GAUGE_UPDATE(wxString(_T("1/4")), 20, gaugeEventReciever, __LINE__);
                num=0;
                #endif

                if(!fetchAndStore("http://api.t.sina.com.cn/statuses/followers.xml", user_id, "T_followers_record", INIT_RECORD_COUNT,-1 ,goalNum))
                {
                    setSystemParam("recordCorrupted","true");
                    throw std::runtime_error("Initialize record data failed");
                }

            }
            #ifdef GUI_DEPENDENT
            TOTAL_GAUGE_UPDATE(wxString(_T("1/4")),60, gaugeEventReciever,__LINE__);
            #endif
        }
        catch(wxSQLite3Exception e)
        {
            #ifdef SQL_DEBUG
                wxMessageBox(wxString("When initialize followers_record data, ")<<(e.GetErrorCode()+":"+e.GetMessage()));
            #endif
            #ifdef SQL_LOG
                LOGMAN.logError(wxString("When initialize followers_record data, ")<<(e.GetErrorCode()+":"+e.GetMessage()));
            #endif
            return false;
        }
        catch(std::runtime_error& e)
        {
            #ifdef SQL_LOG
                LOGMAN.logError(e.what());
            #endif
            return false;
        }
    }
    else
    {
        #ifdef SQL_DEBUG
            wxMessageBox("When find unfo , fetch and store followers or friends ids failed");
        #endif
        #ifdef SQL_LOG
            LOGMAN.logError("When find unfo , fetch and store followers or friends ids failed");
        #endif
        return false;
    }

    return true;
}

bool CFollowersQueryInc::deleteFollowers(tableType& infoTable , const wxString& tableName)
{
    typedef std::map<wxString,wxString> rowType;
    wxString sqlStr("DELETE FROM "+tableName+" WHERE ");
    wxString idStr("");

    if(infoTable.size()!=0)
    {
        for(tableType::iterator vec_itr = infoTable.begin(); vec_itr != infoTable.end(); ++vec_itr)
        {
            if(vec_itr != infoTable.begin())
            {
                idStr += " OR ";
            }

            idStr += "id='"+(*vec_itr)["id"]+"'";
        }

        sqlStr += idStr;
        #ifdef SQL_DEBUG
            wxMessageBox(sqlStr);
        #endif
        try
        {
            db.ExecuteUpdate(sqlStr);
        }
        catch(wxSQLite3Exception e)
        {
            #ifdef SQL_DEBUG
                wxMessageBox(wxString("When delete followers, ")<<(e.GetErrorCode()+":"+e.GetMessage()+"-->SQL: "+sqlStr));
            #endif
            #ifdef SQL_LOG
                LOGMAN.logError(wxString("When delete followers, ")<<(e.GetErrorCode()+":"+e.GetMessage()+"-->SQL: "+sqlStr));
            #endif
            return false;
        }
    }

    return true;
}

bool CFollowersQueryInc::clearTable(const wxString& tableName)
{
    try
    {
        db.ExecuteUpdate(wxString("DELETE FROM ")<<tableName);
    }
    catch(wxSQLite3Exception e)
    {
        #ifdef SQL_DEBUG
            wxMessageBox(wxString("When clear table ")<<tableName<<wxString(" , ")<<wxString(e.GetErrorCode()+":"+e.GetMessage()));
        #endif
        #ifdef SQL_LOG
            LOGMAN.logError(wxString("When clear table ")<<tableName<<wxString(" , ")<<wxString(e.GetErrorCode()+":"+e.GetMessage()));
        #endif
        return false;
    }

    return true;
}

bool CFollowersQueryInc::find_unfollowed(const wxString& user_id)
{
    unfollowed.clear();

    unfollowed = compare();
    #ifdef GUI_DEPENDENT
    SUB_GAUGE_UPDATE(wxString(_T("正在对比数据...")), -1, gaugeEventReciever, __LINE__);
    TOTAL_GAUGE_UPDATE(wxString(_T("2/4")), 70, gaugeEventReciever, __LINE__);
    SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
    #endif

    if(unfollowed.size() != 0)
    {
        #ifdef GUI_DEPENDENT
        SUB_GAUGE_UPDATE(wxString(_T("存入粉丝回收站...")), -1, gaugeEventReciever, __LINE__);
        TOTAL_GAUGE_UPDATE(wxString(_T("2/4")),75, gaugeEventReciever, __LINE__);
        SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
        #endif
        if(intoRecycler(unfollowed))
        {
            #ifdef GUI_DEPENDENT
            SUB_GAUGE_UPDATE(wxString(_T("删除退粉数据...")), -1, gaugeEventReciever, __LINE__);
            TOTAL_GAUGE_UPDATE(wxString(_T("2/4")),80, gaugeEventReciever, __LINE__);
            SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
            #endif
            if(deleteFollowers(unfollowed))
            {
                return true;
            }
            else
            {
                #ifdef SQL_DEBUG
                    wxMessageBox("When find unfo , delete unfo failed");
                #endif
                #ifdef SQL_LOG
                    LOGMAN.logError("When find unfo , delete unfo failed");
                #endif
                return false;
            }
        }
        else
        {
            #ifdef SQL_DEBUG
                wxMessageBox("When find unfo , store to recycler failed");
            #endif
            #ifdef SQL_LOG
                LOGMAN.logError("When find unfo , store to recycler failed");
            #endif
            return false;
        }
    }
    else
    {
        #ifdef GUI_DEPENDENT
        SUB_GAUGE_UPDATE(wxString(_T("没有人退粉...")), -1, gaugeEventReciever, __LINE__);
        TOTAL_GAUGE_UPDATE(wxString(_T("2/4")),80, gaugeEventReciever, __LINE__);
        SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
        #endif
        // Got none unfollowed , that's normal
        return false;
    }
}

bool CFollowersQueryInc::update_new_followed(const wxString& user_id)
{
    #ifdef GUI_DEPENDENT
    SUB_GAUGE_UPDATE(wxString(_T("获取新粉丝数据...")), -1, gaugeEventReciever, __LINE__);
    TOTAL_GAUGE_UPDATE(wxString(_T("3/4")),80, gaugeEventReciever, 619);
    SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
    #endif

    int newFollowers = static_cast<int>(countNewFollowers());
    if(newFollowers != 0)
    {
        int needCursor = 0;
        if(newFollowers>200)
        {
            if(newFollowers%200!=0)
            {
                newFollowers = newFollowers%200;
                needCursor = newFollowers/200*200;
            }
            else    //for example , 400>200 but new = 400%200, new ==0
            {
                newFollowers = (newFollowers-1)%200;
                needCursor = (newFollowers-1)/200*200;
            }
        }
        #ifdef GUI_DEPENDENT
        SUB_GAUGE_UPDATE(wxString(_T("保存新粉丝数据...")), -1, gaugeEventReciever, __LINE__);
        TOTAL_GAUGE_UPDATE(wxString(_T("3/4")),85, gaugeEventReciever, __LINE__);
        SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
        #endif
        if(fetchAndStore("http://api.t.sina.com.cn/statuses/followers.xml", user_id, "T_followers_record", newFollowers, needCursor))
        {
            #ifdef GUI_DEPENDENT
            TOTAL_GAUGE_UPDATE(wxString(_T("3/4")),95, gaugeEventReciever, __LINE__);
            SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
            #endif

            return true;
        }
    }

    #ifdef GUI_DEPENDENT
    SUB_GAUGE_UPDATE(wxString(_T("没有新粉丝...")), -1, gaugeEventReciever, 931);
    TOTAL_GAUGE_UPDATE(wxString(_T("3/4")), 95, gaugeEventReciever, __LINE__);
    SUB_GAUGE_LOOP_PULSE(gaugeEventReciever);
    #endif
    return false;
}
